﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
using System.ServiceModel;
using GUI.ServiceAgents;
using System.Diagnostics;
using GUI.AirportSVC;
using System.Collections.Generic;
using System.Windows.Data;
using GUI;
using GUI.Commanding;

namespace GUI.ViewModels
{
    public class MainPageViewModel : ViewModelBase
    {
        // Constants
        public const int _runwayId24L = 0;
        public const int _runwayId24R = 1;
        public const int _runwayId25L = 2;
        public const int _runwayId25R = 3;

        private ObservableCollection<Flight> _flightList = new ObservableCollection<Flight>();
        private Flight _selectedFlight;
        private Plane _selectedPlane;
        private Node _selectedDestinationNode;
        private Map _airportMap;
        private string _selectedRoutePathString;
        private Suggestion _currentSuggestion;

        private ObservableCollection<Node> _runwayNodes = new ObservableCollection<Node>();
        private ObservableCollection<string> _runwayPathStrings = new ObservableCollection<string>();
        private ObservableCollection<Node> _taxiwayNodes = new ObservableCollection<Node>();
        private ObservableCollection<string> _taxiwayPathStrings = new ObservableCollection<string>();
        private ObservableCollection<Node> _gateNodes = new ObservableCollection<Node>();
        private ObservableCollection<Plane> _activePlaneList = new ObservableCollection<Plane>();

        public MainPageViewModel()
            : this(new AirportServiceAgent())
        {
        }

        public MainPageViewModel(IAirportServiceAgent airportServiceAgent)
        {
            if (!IsDesignTime)
            {
                WireCommands();
                if (airportServiceAgent != null) 
                    AServiceAgent = airportServiceAgent;
                //ConnectToAirportService();
                //RegisterForPlaneUpdates();
                
                // Get airport map from Airport Service
                GetAirportLayout();
                GetCurrentPlaneList();
                RegisterForPlaneUpdates();
            }
        }

        /*
        public ~MainPageViewModel()
        {
            
        }
        */

        #region MainPageViewModel Properties

        public string SelectedRoutePathString
        {
            get
            {
                return _selectedRoutePathString;
            }

            set
            {
                if (_selectedRoutePathString != value)
                {
                    _selectedRoutePathString = value;
                    OnPropertyChanged("SelectedRoutePathString");
                }
            }
        }

        public Map AirportMap
        {
            get { return _airportMap; }

            set
            {
                if (_airportMap != value)
                {
                    _airportMap = value;
                    OnPropertyChanged("AirportMap");
                }
            }
        }

        // Nodes to be overlayed on the map
        public ObservableCollection<Node> RunwayNodes
        {
            get { return _runwayNodes; }

            set
            {
                if (_runwayNodes != value)
                {
                    _runwayNodes = value;
                    OnPropertyChanged("RunwayNodes");
                }
            }
        }

        public ObservableCollection<string> RunwayPathStrings
        {
            get { return _runwayPathStrings; }

            set
            {
                if (_runwayPathStrings != value)
                {
                    _runwayPathStrings = value;
                    OnPropertyChanged("RunwayPathStrings");
                }
            }
        }

        public ObservableCollection<string> TaxiwayPathStrings
        {
            get { return _taxiwayPathStrings; }

            set
            {
                if (_taxiwayPathStrings != value)
                {
                    _taxiwayPathStrings = value;
                    OnPropertyChanged("TaxiwayPathStrings");
                }
            }
        }

        public ObservableCollection<Node> TaxiwayNodes
        {
            get { return _taxiwayNodes; }

            set
            {
                if (_taxiwayNodes != value)
                {
                    _taxiwayNodes = value;
                    OnPropertyChanged("TaxiwayNodes");
                }
            }
        }

        public ObservableCollection<Node> GateNodes
        {
            get { return _gateNodes; }

            set
            {
                if (_gateNodes != value)
                {
                    _gateNodes = value;
                    OnPropertyChanged("GateNodes");
                }
            }
        }

        public ObservableCollection<Flight> FlightList
        {
            get { return _flightList; }

            set
            {
                if (_flightList != value)
                {
                    _flightList = value;
                    OnPropertyChanged("FlightList");
                }
            }
        }

        public ObservableCollection<Plane> ActivePlaneList
        {
            get { return _activePlaneList; }

            set
            {
                if (_activePlaneList != value)
                {
                    _activePlaneList = value;
                    OnPropertyChanged("ActivePlaneList");
                }
            }
        }

        public Flight SelectedFlight
        {
            get { return _selectedFlight; }

            set
            {
                if (_selectedFlight != value)
                {
                    _selectedFlight = value;

                    string newRoutePathString = "";

                    Collection<RouteNode> newRouteNodes/* = _selectedFlight.FlightPlane.CurrentRoute.RouteNodes*/;
                    foreach (Plane plane in _activePlaneList)
                    {
                        if (plane.PlaneId == _selectedFlight.FlightId)
                        {
                            newRouteNodes = plane.CurrentRoute.RouteNodes;

                            if (newRouteNodes.Count > 0)
                            {
                                IEnumerator<RouteNode> enumerator = newRouteNodes.GetEnumerator();
                                enumerator.MoveNext();

                                // Set StartPoint for PathFigure to first Node of the Route to display
                                newRoutePathString = "M ";
                                newRoutePathString += enumerator.Current.XPos;
                                newRoutePathString += ",";
                                newRoutePathString += enumerator.Current.YPos;
                                newRoutePathString += " ";

                                enumerator.MoveNext();

                                // Add path segments 
                                for (; enumerator.MoveNext(); )
                                {
                                    newRoutePathString += "L ";
                                    newRoutePathString += enumerator.Current.XPos;
                                    newRoutePathString += ",";
                                    newRoutePathString += enumerator.Current.YPos;
                                    newRoutePathString += " ";
                                }
                            }

                            //Debug.WriteLine("newRoutePathString =  " + newRoutePathString);
                            SelectedRoutePathString = newRoutePathString;

                            // Set Selected Plane to the Flight's associated Plane object
                            SelectedPlane = plane;
                            SelectedDestinationNode = plane.DestinationNode;
                            
                            break;
                        }
                    }
                    
                    
                    OnPropertyChanged("SelectedFlight");

                    SetRunway24LCommand.IsEnabled = true;
                    SetRunway24RCommand.IsEnabled = true;
                    SetRunway25LCommand.IsEnabled = true;
                    SetRunway25RCommand.IsEnabled = true;
                }
            }
        }

        public Plane SelectedPlane
        {
            get
            {
                return _selectedPlane;
            }

            set
            {
                if (_selectedPlane != value)
                {
                    _selectedPlane = value;
                    OnPropertyChanged("SelectedPlane");
                }
            }
        }

        public Node SelectedDestinationNode
        {
            get
            {
                return _selectedDestinationNode;
            }

            set
            {
                if (_selectedDestinationNode != value)
                {
                    _selectedDestinationNode = value;
                    OnPropertyChanged("SelectedDestinationNode");
                }
            }
        }

        public Suggestion CurrentSuggestion
        {
            get
            {
                return _currentSuggestion;
            }

            set
            {
                if (_currentSuggestion != value)
                {
                    _currentSuggestion = value;

                    string newRoutePathString = "";

                    Collection<RouteNode> newRouteNodes = _currentSuggestion.SuggestionRoute.RouteNodes;

                    if (newRouteNodes.Count > 0)
                    {
                        IEnumerator<RouteNode> enumerator = newRouteNodes.GetEnumerator();
                        enumerator.MoveNext();

                        // Set StartPoint for PathFigure to first Node of the Route to display
                        newRoutePathString = "M ";
                        newRoutePathString += enumerator.Current.XPos;
                        newRoutePathString += ",";
                        newRoutePathString += enumerator.Current.YPos;
                        newRoutePathString += " ";

                        enumerator.MoveNext();

                        // Add path segments 
                        for (; enumerator.MoveNext(); )
                        {
                            newRoutePathString += "L ";
                            newRoutePathString += enumerator.Current.XPos;
                            newRoutePathString += ",";
                            newRoutePathString += enumerator.Current.YPos;
                            newRoutePathString += " ";
                        }
                    }

                    Debug.WriteLine("newRoutePathString =  " + newRoutePathString);
                    SelectedRoutePathString = newRoutePathString;
                }
            }
        }
#endregion

        #region Buttons

        private void WireCommands()
        {
            SetRunway24LCommand = new RelayCommand(Button24L_Click);
            SetRunway24RCommand = new RelayCommand(Button24R_Click);
            SetRunway25LCommand = new RelayCommand(Button25L_Click);
            SetRunway25RCommand = new RelayCommand(Button25R_Click);
            AcceptSuggestionCommand = new RelayCommand(ButtonAcceptSuggestedRoute_Click);
        }

        public void Button24L_Click()
        {
            Node destNode = new Node();
            destNode.XPos = 624;
            destNode.YPos = 83;
            destNode.NodeId = _runwayId24L;
            
            SelectedDestinationNode = destNode;
            GetSuggestion(SelectedPlane, destNode);

            // Enable Accept button now that a runway has been selected
            AcceptSuggestionCommand.IsEnabled = true;
        }       

        public void Button24R_Click()
        {
            Node destNode = new Node();
            destNode.XPos = 618;
            destNode.YPos = 42;
            destNode.NodeId = _runwayId24R;

            SelectedDestinationNode = destNode;
            GetSuggestion(SelectedPlane, destNode);

            // Enable Accept button now that a runway has been selected
            AcceptSuggestionCommand.IsEnabled = true;
        }

        public void Button25L_Click()
        {
            Node destNode = new Node();
            destNode.XPos = 959;
            destNode.YPos = 359;
            destNode.NodeId = _runwayId25L;

            SelectedDestinationNode = destNode;
            GetSuggestion(SelectedPlane, destNode);

            // Enable Accept button now that a runway has been selected
            AcceptSuggestionCommand.IsEnabled = true;
        }

        public void Button25R_Click()
        {
            Node destNode = new Node();
            destNode.XPos = 1012;
            destNode.YPos = 306;
            destNode.NodeId = _runwayId25R;

            SelectedDestinationNode = destNode;
            GetSuggestion(SelectedPlane, destNode);

            // Enable Accept button now that a runway has been selected
            AcceptSuggestionCommand.IsEnabled = true;
        }

        public void ButtonAcceptSuggestedRoute_Click()
        {
            AcceptSuggestion(_currentSuggestion);
        }

        public RelayCommand SetRunway24LCommand
        {
            get;
            private set;
        }

        public RelayCommand SetRunway24RCommand
        {
            get;
            private set;
        }

        public RelayCommand SetRunway25LCommand
        {
            get;
            private set;
        }

        public RelayCommand SetRunway25RCommand
        {
            get;
            private set;
        }

        public RelayCommand AcceptSuggestionCommand
        {
            get;
            private set;
        }

        #endregion

        private IAirportServiceAgent AServiceAgent { get; set; }

        private void ConnectToAirportService()
        {
            // Both of these are equivalent
            AServiceAgent.ConnectToAirportService((s, e) => c_newFlightUpdateReceived(s, e));
            //AServiceAgent.ConnectToAirportService(new EventHandler<newFlightUpdateReceivedEventArgs>(c_newFlightUpdateReceived));
        }

        private void c_newFlightUpdateReceived(object sender, AirportSVC.newFlightUpdateReceivedEventArgs e)
        {
            try
            {
                if (e.Error == null)
                {
                    // Add the flight to the list of active flights
                    FlightList.Add(e.newFlight);

                    // Add the plane to the list of active planes, if it's not already there
                    bool planeFound = false;
                    foreach (Plane plane in _activePlaneList)
                    {
                        //if (plane.PlaneId == e.newFlight.FlightPlane.PlaneId)
                        if (plane.PlaneId == e.newFlight.FlightId)
                        {
                            planeFound = true;
                            break;
                        }
                    }

                    if (!planeFound)
                    {
                        // This won't work when Flights don't have a pointer to a plane, use UpdatePlane
                        //ActivePlaneList.Add(e.newFlight.FlightPlane);
                    } 
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine("MainPageViewModel: {0}", ex.Message);
                //Debug.WriteLine("                   {0}", ex.StackTrace);
                //Debug.WriteLine("                   {0}", ex.InnerException.Message);
            }
        }

        private void RegisterForPlaneUpdates()
        {
            AServiceAgent.RegisterForPlaneUpdates((s, e) => c_updatedPlaneReceived(s, e));
        }
       
        private void c_updatedPlaneReceived(object sender, AirportSVC.UpdatePlaneReceivedEventArgs e)
        {
            try
            {
                if (e.Error == null)
                {

                    switch (e.updatedPlane.UpdateType)
                    {
                        case PlaneUpdateType.AddNewPlane:
                            ActivePlaneList.Add(e.updatedPlane);
                            if (e.updatedPlane.PlaneFlight != null)
                            {
                                FlightList.Add(e.updatedPlane.PlaneFlight);
                            }
                            break;

                        case PlaneUpdateType.UpdateExistingPlane:
                            for (int i = 0; i < ActivePlaneList.Count; i++)
                            {
                                if (ActivePlaneList[i].PlaneId == e.updatedPlane.PlaneId)
                                {                                    
                                    ActivePlaneList[i] = e.updatedPlane;

                                    for (int j = 0; j < FlightList.Count; j++)
                                    {
                                        //if (FlightList[j].FlightPlane.PlaneId == e.updatedPlane.PlaneId)
                                        if (FlightList[j].FlightId == e.updatedPlane.PlaneId)
                                        {
                                            if (e.updatedPlane.PlaneFlight == null)
                                            {
                                                FlightList.RemoveAt(j);
                                            }
                                            /*
                                            else
                                            {
                                                FlightList[j].FlightPlane = e.updatedPlane;
                                            }
                                            */
                                            break;
                                        }
                                    }

                                    break;
                                }
                            }
                            break;

                        case PlaneUpdateType.DeletePlane:
                            // TODO Implement Delete functionality
                            break;

                        default:
                            break;  
                    }                                                            
                    
                    /*
                    // Update the plane in FlightList
                    // TODO Be able to handle plane without an associated Plane
                    for (int j = 0; j < FlightList.Count; j++)
                    {
                        if (FlightList[j].FlightPlane.PlaneId == e.updatedPlane.PlaneId)
                        {
                            FlightList[j].FlightPlane = e.updatedPlane;
                        }
                    }
                    */
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine("MainPageViewModel: {0}", ex.Message);
            }
        }

        private void GetAirportLayout()
        {
            AServiceAgent.GetAirportLayout((s, e) => c_airportLayoutReceived(s, e));
        }

        private void c_airportLayoutReceived(object sender, AirportSVC.IncomingAirportLayoutReceivedEventArgs e)
        {
            try
            {
                if (e.Error == null)
                {
                    AirportMap = e.airportMap;
                }

                if (_airportMap == null)
                {
                    throw new Exception("_airportMap is null");
                }

                List<Node> runwayNodeList = new List<Node>();
                List<string> runwayPathStringList = new List<string>();
                // Runways
                foreach (Runway runway in _airportMap.Runways)
                {
                    runwayNodeList.AddRange(runway.Nodes);

                    // Construct Path string for this runway
                    string newRunwayPathString;                   

                    // Set StartPoint for PathFigure to first Node of the Runway to display
                    newRunwayPathString = "M ";
                    newRunwayPathString += runway.Nodes[0].XPos;
                    newRunwayPathString += ",";
                    newRunwayPathString += runway.Nodes[0].YPos;
                    newRunwayPathString += " ";                                       

                    // Add path segments 
                    for (int i = 0; i < runway.Nodes.Count; i++)
                    {
                        newRunwayPathString += "L ";
                        newRunwayPathString += runway.Nodes[i].XPos;
                        newRunwayPathString += ",";
                        newRunwayPathString += runway.Nodes[i].YPos;
                        newRunwayPathString += " ";
                    }
                    runwayPathStringList.Add(newRunwayPathString);
                }
                RunwayNodes = new ObservableCollection<Node>(runwayNodeList);
                RunwayPathStrings = new ObservableCollection<string>(runwayPathStringList);

                // Taxiways
                List<Node> taxiwayNodeList = new List<Node>();
                List<string> taxiwayPathStringList = new List<string>();
                foreach (Taxiway taxiway in _airportMap.Taxiways)
                {
                    taxiwayNodeList.AddRange(taxiway.Nodes);

                    // Construct Path string for this runway
                    string newTaxiwayPathString;                    
                    
                    // Set StartPoint for PathFigure to first Node of the Runway to display
                    newTaxiwayPathString = "M ";
                    newTaxiwayPathString += taxiway.Nodes[0].XPos;
                    newTaxiwayPathString += ",";
                    newTaxiwayPathString += taxiway.Nodes[0].YPos;
                    newTaxiwayPathString += " ";                    

                    // Add path segments 
                    for (int j = 0; j < taxiway.Nodes.Count; j++)
                    {
                        newTaxiwayPathString += "L ";
                        newTaxiwayPathString += taxiway.Nodes[j].XPos;
                        newTaxiwayPathString += ",";
                        newTaxiwayPathString += taxiway.Nodes[j].YPos;
                        newTaxiwayPathString += " ";
                    }
                    taxiwayPathStringList.Add(newTaxiwayPathString);
                    //Debug.WriteLine(newTaxiwayPathString);
                }
                TaxiwayNodes = new ObservableCollection<Node>(taxiwayNodeList);
                TaxiwayPathStrings = new ObservableCollection<string>(taxiwayPathStringList);

                // Gates
                List<Node> gateNodeList = new List<Node>();
                foreach (Gate gate in _airportMap.Gates)
                {
                    gateNodeList.AddRange(gate.Nodes);
                }
                GateNodes = new ObservableCollection<Node>(gateNodeList);                

            }
            catch (Exception ex)
            {
                Debug.WriteLine("MainPageViewModel: {0}", ex.Message);
                //Debug.WriteLine("                   {0}", ex.StackTrace);
                //Debug.WriteLine("                   {0}", ex.InnerException.Message);
            }
        }

        private void GetCurrentPlaneList()
        {
            AServiceAgent.GetCurrentPlaneList((s, e) => c_currentPlaneListReceived(s, e));
        }

        private void c_currentPlaneListReceived(object sender, AirportSVC.SendCurrentPlaneListReceivedEventArgs e)
        {
            try
            {
                if (e.Error == null)
                {
                    List<Flight> currentFlightList = new List<Flight>();

                    // Iterate through the planes in the plane list and add their Flights (if available)
                    // to the FlightList
                    foreach (Plane plane in e.planeList)
                    {
                        if (plane.PlaneFlight != null)
                        {
                            currentFlightList.Add(plane.PlaneFlight);
                        }
                    }

                    FlightList = new ObservableCollection<Flight>(currentFlightList);

                    ActivePlaneList = e.planeList;
                }

                if (_activePlaneList == null)
                {
                    throw new Exception("_activePlaneList is null");
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine("MainPageViewModel: {0}", ex.Message);
            }
        }

        private void GetSuggestion(Flight flight, Node destinationNode)
        {
            try
            {
                for (int i = 0; i < ActivePlaneList.Count; i++)
                {
                    if (ActivePlaneList[i].PlaneId == flight.FlightId)
                    {
                        AServiceAgent.GetSuggestion(ActivePlaneList[i], destinationNode, (s, e) => c_suggestionReceived(s, e));
                        break;
                    }
                }                
            }
            catch (Exception ex)
            {
                Debug.WriteLine("MainPageViewModel: {0}", ex.Message);
            }
        }

        private void GetSuggestion(Plane plane, Node destinationNode)
        {
            try
            {
                AServiceAgent.GetSuggestion(plane, destinationNode, (s, e) => c_suggestionReceived(s, e));
            }
            catch (Exception ex)
            {
                Debug.WriteLine("MainPageViewModel: {0}", ex.Message);
            }
        }

        private void c_suggestionReceived(object sender, AirportSVC.SendSuggestionReceivedEventArgs e)
        {
            try
            {
                if (e.Error == null)
                {
                    /*
                    int updatedFlightPlaneIndex = ActivePlaneList.IndexOf(e.pendingSuggestion.SuggestionFlight.FlightPlane);
                    if (updatedFlightPlaneIndex == -1)
                    {
                        throw new Exception("Received Suggested Route for nonexistant Plane");
                    }
                    */
                    
                    // Update local copy of the updated Flight with the new suggested route
                    CurrentSuggestion = e.pendingSuggestion;
                }
                else
                {
                    throw new Exception(e.Error.Message);
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine("MainPageViewModel: {0}", ex.Message);
            }
        }

        private void AcceptSuggestion(Suggestion acceptedSuggestion)
        {
            try
            {
                acceptedSuggestion.SuggestionPlane.DestinationNode = SelectedDestinationNode;
                AServiceAgent.AcceptSuggestion(acceptedSuggestion);
            }
            catch (Exception ex)
            {
                Debug.WriteLine("MainPageViewModel: {0}", ex.Message);
            }
        }
    }

    // Class to format values for display in GUI
    public class RunwayFormatter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {                        
            switch ((int)value)
            {
                case MainPageViewModel._runwayId24L:
                    return "Runway 24L";
                    break;
                case MainPageViewModel._runwayId24R:
                    return "Runway 24R";
                    break;
                case MainPageViewModel._runwayId25L:
                    return "Runway 25L";
                    break;
                case MainPageViewModel._runwayId25R:
                    return "Runway 25R";
                    break;
                default:
                    return "No Endpoint Selected";
            }            
        }
     
        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }           
    }

    // Convert Node to a string consisting of XPos and YPos for tooltip
    public class NodeFormatter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            Node input = (Node)value;

            return "(" + input.XPos + ", " + input.YPos + ")";
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

    // Class to bind the canvas position to xPos and yPos of each map Node
    public class MapNodesItemsControl : ItemsControl
    {
        protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
        {
            FrameworkElement contentitem = element as FrameworkElement;
            Binding leftBinding = new Binding("XPos");
            Binding topBinding = new Binding("YPos");
            contentitem.SetBinding(Canvas.LeftProperty, leftBinding);
            contentitem.SetBinding(Canvas.TopProperty, topBinding);
            base.PrepareContainerForItemOverride(element, item);
        }
    }

    public class RunwayLinesItemsControl : ItemsControl
    {
        /*
        protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
        {
            FrameworkElement contentitem = element as FrameworkElement;
            Binding startXBinding = new Binding("Endpoint1.XPos");
            Binding startYBinding = new Binding("Endpoint1.YPos");
            Binding endXBinding = new Binding("Endpoint2.XPos");
            Binding endYBinding = new Binding("Endpoint2.YPos");

            //contentitem.SetBinding(Canvas.LeftProperty, leftBinding);
            //contentitem.SetBinding(Canvas.TopProperty, topBinding);

            base.PrepareContainerForItemOverride(element, item);
        }
         * */
    }

    public class PlaneIconsItemsControl : ItemsControl
    {
        protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
        {
            try
            {
                FrameworkElement contentitem = element as FrameworkElement;
                Binding leftBinding = new Binding("Location.XPos");
                Binding topBinding = new Binding("Location.YPos");

                contentitem.SetBinding(Canvas.LeftProperty, leftBinding);
                contentitem.SetBinding(Canvas.TopProperty, topBinding);

                base.PrepareContainerForItemOverride(element, item);
            }
            catch (Exception ex)
            {
                Debug.WriteLine("MainPageViewModel: {0}", ex.Message);
            }
        }
    }

}
